package com.turtle3d.formallanguage.grammar;

import java.util.ArrayList;
import java.util.Hashtable;

import com.turtle3d.Randomizer;
import com.turtle3d.formallanguage.grammar.parser.SimpleProductionParser;
import com.turtle3d.formallanguage.symbol.Method;
import com.turtle3d.formallanguage.symbol.MethodAdapter;
import com.turtle3d.formallanguage.symbol.Symbol;
import com.turtle3d.formallanguage.symbol.SymbolClass;
import com.turtle3d.formallanguage.symbol.SymbolFactory;




/**
 * 
 * @author marek
 *
 */
public class Grammar {
	
	private String name = "LSystemsGrammar";
	private Symbol axiom;
	
	private Hashtable<Symbol, ProductionFamily> productions = new Hashtable<Symbol, ProductionFamily>();
	
	private ArrayList<Symbol> outputSymbolSequence = new ArrayList<Symbol>();
	
	/**
	 * If a method needs a parameter that is not defined in orginating symbol,
	 * it takes default one. 
	 */
	private Hashtable<String, Float> defaultParameters = new Hashtable<String, Float>();
	
	private int iteration=0;
	
	/**
	 * Slow.
	 * @param iterations
	 * @return
	 */
	@Deprecated
	public ArrayList<Symbol> generateSymbolSequence(int iterations){
		ArrayList<Symbol> symbols = new ArrayList<Symbol>();
		
		symbols.add(axiom);
		
		for(iteration=0;iteration<iterations;iteration++)
			nextIteration(symbols);			
		
		return symbols;
	}
	
	public ArrayList<Symbol> generateSymbolSequence2(int iterations){
		ArrayList<Symbol> symbols = new ArrayList<Symbol>();
		
		symbols.add(axiom);
		
		for(iteration=0;iteration<iterations;iteration++)
			symbols=nextIteration2(symbols);			
		
		return symbols;
	}
	
	/**
	 * Slow.
	 * @param symbolSequence
	 * @param showParameters
	 * @return
	 */
	@Deprecated
	public static String printSymbolSequence(ArrayList<Symbol> symbolSequence, boolean showParameters){
		String ret="";
		for(Symbol symbol:symbolSequence){
			if(showParameters)
				ret+=symbol;
			else
				ret+=symbol.getSymbol();
		}
		
		return ret;	
		
	}
	
	public Production getProduction(Symbol leftSide){
		return productions.get(leftSide).getProduction();
	}
	
	public ArrayList<Symbol> nextIteration(ArrayList<Symbol> symbols){
		
		int i=0;
		try
		{
			while(true){
				Symbol symbol = symbols.get(i); 
				
				if(productions.containsKey(symbol)){
					ArrayList<SymbolClass> rightSide = getProduction(symbol).getRightSide(); 
					
					Symbol removedSymbol = symbols.remove(i);									
					
					//switch
					for(int j=0;j<rightSide.size();j++){
						Symbol s = new Symbol(rightSide.get(j));
						s.setParameter("iteration", iteration);
						boolean useThisSymbol = true;
							//Parametric calculations
							for(Method method: s.getSymbolClass().getMethods()){
								useThisSymbol=method.calculate(removedSymbol, s, defaultParameters); //NullPointer!
							}							
												
						if(useThisSymbol)
							symbols.add(i+j, s);																				
						else
							i--;
						
					}
					//symbols.addAll(i, rightSide);					
					
					i+=rightSide.size();
				}
				else
				{
					//System.out.println("No production for "+symbol+" symbol.");
					i++;
				}
				
			}
		}
		catch (IndexOutOfBoundsException e)
		{			
			return symbols;
		}
		
		
	}
	
	public ArrayList<Symbol> nextIteration2(ArrayList<Symbol> symbolSequence){
		
		ArrayList<Symbol> output = new ArrayList<Symbol>();
		
		for(Symbol symbol: symbolSequence)
		{
			if(productions.containsKey(symbol)){
				ArrayList<SymbolClass> rightSide = getProduction(symbol).getRightSide();
				
				//Compute production
				for(int j=0;j<rightSide.size();j++){
					Symbol s = new Symbol(rightSide.get(j));
					s.setParameter("iteration", iteration);
					boolean useThisSymbol = true;
					
						//Parametric calculations
						for(Method method: s.getSymbolClass().getMethods()){
							useThisSymbol=method.calculate(symbol, s, defaultParameters); //NullPointer!
						}							
											
					if(useThisSymbol)
						output.add(s);					
					
				}
				
			}
			else
			{
				//just rewrite symbol
				output.add(symbol);
			}
			
			
		}
		
		return output;
		
	}
	
	
	public void addProduction(Production production){
		if(productions.get(production.getLeftSide())==null)
			productions.put(production.getLeftSide(), new ProductionFamily(production.getLeftSide()));
		
		productions.get(production.getLeftSide()).addProduction(production);				
	}

	public Symbol getAxiom()
	{
		return axiom;
	}

	public void setAxiom(Symbol axiom)
	{
		this.axiom = axiom;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}
	
	public void setDefaultParameter(String name, Float value){
		defaultParameters.put(name, value);
	}
	
	public Float getDefaultParameter(String name){
		return defaultParameters.get(name);
	}
	
	public static Grammar createTestGrammar2(){
		Grammar g = new Grammar();
		//g.setDefaultParameter("radius", 0.02f);
		//g.setDefaultParameter("distance", 0.2f);
		Symbol axiom = new Symbol("B");		
		g.setAxiom(axiom);
		
		SymbolClass radiusGrowth = new SymbolClass("F", "F(r*1.8)");		
		radiusGrowth.addMethod(new MethodAdapter(){
			public boolean calculate(Symbol calculatedSymbol)
			{
				Float r = this.getParameter("radius");
				Float r_mul = this.getParameter("radiusMultiplier");
				calculatedSymbol.setParameter("radius", r*r_mul);
				
				Float d = this.getParameter("distance");
				Float d_mul = this.getParameter("distanceMultiplier");
				calculatedSymbol.setParameter("distance", d*d_mul);
				
				return true;
				
			}
		});				
		SymbolFactory.getSymbolFactory().registerSymbolClass(radiusGrowth);
		SymbolClass bClass = new SymbolClass("B","B");
		bClass.addMethod(new MethodAdapter(){
			@Override
			public boolean calculate(Symbol calculatedSymbol)
			{
				float i = calculatedSymbol.getParameter("iteration");
				float iC = calculatedSymbol.getParameter("iterationsCount");
				float branchChance = calculatedSymbol.getParameter("branchChance");
				
				return Randomizer.getRandomizer().getRandom().nextFloat()<( (i+branchChance*iC)/iC);
				//return true;
				
			}
		});
		SymbolFactory.getSymbolFactory().registerSymbolClass(bClass);
		
		g.addProduction(SimpleProductionParser.parse("B->F[+RB][-RB][<+RB][>+RB]"));
		//g.addProduction(SimpleProductionParser.parse("B->F[+RB][-RB]"));
		//g.addProduction(SimpleProductionParser.parse("B->F[<+RB][>+RB]"));
		//g.addProduction(SimpleProductionParser.parse("B->F[<+RB][+RB][>+RB]"));
		g.addProduction(SimpleProductionParser.parse("F->F(r*1.8)"));
		
		return g;
	}
				
	public static Grammar createTestGrammar(){
		Grammar g = new Grammar();
		g.setDefaultParameter("radius", 0.02f);
		Symbol axiom = new Symbol("B");		
		g.setAxiom(axiom);
		
		Production p = new Production();
		p.setLeftSide(new Symbol("B"));		
		ArrayList<SymbolClass> rightSide = new ArrayList<SymbolClass>();	
		SymbolClass radiusGrowth = new SymbolClass("F", "F(r*1.8)");		
		radiusGrowth.addMethod(new MethodAdapter(){
			public boolean calculate(Symbol calculatedSymbol)
			{
				Float r = this.getParameter("radius");								
				calculatedSymbol.setParameter("radius", r*1.8f);
				return true;
							
			}
		});		
		rightSide.add(radiusGrowth);
		
		SymbolClass bClass = new SymbolClass("B","B");
		SymbolClass plusClass = new SymbolClass("+","+");
		SymbolClass minusClass = new SymbolClass("-","-");
		SymbolClass leftClass = new SymbolClass("[","[");
		SymbolClass rightClass = new SymbolClass("]","]");
		//[-B][+B]
		rightSide.add(leftClass);
		rightSide.add(minusClass);
		rightSide.add(bClass);
		rightSide.add(rightClass);
		rightSide.add(leftClass);
		rightSide.add(plusClass);
		rightSide.add(bClass);
		rightSide.add(rightClass);
													
		p.setRightSide(rightSide);
		
		g.addProduction(p);
		
		Production p2 = new Production();
		p2.setLeftSide(new Symbol("F"));
		ArrayList<SymbolClass> rightSide2 = new ArrayList<SymbolClass>();
		rightSide2.add(radiusGrowth);
		rightSide2.add(radiusGrowth);
		p2.setRightSide(rightSide2);
		
		g.addProduction(p2);
		
		return g;
		
	}
	
	public static void main(String[] args)
	{
		Grammar g = new Grammar();
		Symbol axiom = new Symbol("F");
		axiom.setParameter("radius", 0.01f);
		g.setAxiom(axiom);
		
		Production p = new Production();
		p.setLeftSide(new Symbol("F"));		
		ArrayList<SymbolClass> rightSide = new ArrayList<SymbolClass>();	
		SymbolClass symbolClass = new SymbolClass("F", "F(r*1.8)");		
		//symbolClass.addMethod(new LMethod());		
		rightSide.add(symbolClass);
		
		SymbolClass symbolClass2 = new SymbolClass("F", "F(r*1.2)");		
		//symbolClass2.addMethod(new LMethod());
		rightSide.add(symbolClass2);		
		p.setRightSide(rightSide);
		
		g.addProduction(p);
		
		ArrayList<Symbol> symbols = g.generateSymbolSequence(3);
		
		for(Symbol s:symbols){
			System.out.print(s);
		}				
		
	}
	
	
}
